package pkg

import (
	"context"
	"fmt"
	"time"

	"github.com/SkyAPM/go2sky"
	"github.com/SkyAPM/go2sky/propagation"
	"github.com/SkyAPM/go2sky/reporter"
	agentV3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"

	"gitee.com/ltotal/ppw_gin/errs"
)

const (
	ComponentRedisServer     = 7
	ComponentIDGINHttpServer = 5006
)

type LocalSpanOptions struct {
	OperationName string
	Component     int32
	Log           []string
	Layer         agentV3.SpanLayer
}

type EntrySpanOptions struct {
	OperationName string
	Component     int32
	Extractor     propagation.Extractor
	Log           []string
	Layer         agentV3.SpanLayer
}

type ExitSpanOptions struct {
	OperationName string
	Component     int32
	Log           []string
	Layer         agentV3.SpanLayer
	Peer          string
	Injector      propagation.Injector
}

type TracerPkg struct {
	tracer *go2sky.Tracer
}

var Tracer = &TracerPkg{}

func (p *TracerPkg) Init(serviceAddr string, serviceName string, enable bool) {
	rp, err := reporter.NewGRPCReporter(serviceAddr, reporter.WithCheckInterval(time.Second))
	if err != nil {
		panic(fmt.Sprintf("go2sky reporter创建失败：%s", err))
	}
	tracer, err := go2sky.NewTracer(serviceName, go2sky.WithReporter(rp))
	if err != nil {
		panic(fmt.Sprintf("go2sky tracer创建失败：%s", err))
	}
	// 初始化时，如果链路跟踪开关打开，则go2sky设置跟踪实例，否则只保留内存副本
	if enable {
		go2sky.SetGlobalTracer(tracer)
	}
	p.tracer = tracer
}

// CreateEntrySpan 创建入口起始span，最终出口调用sky.GetGcm().DeleteContext()
func (p *TracerPkg) CreateEntrySpan(c context.Context, opts EntrySpanOptions) (go2sky.Span, context.Context, error) {
	if !p.CheckEnable() {
		return nil, c, errs.ErrTracerNotEnabled
	}
	span, ctx, err := go2sky.GetGlobalTracer().CreateEntrySpan(c, opts.OperationName, opts.Extractor)
	if err == nil {
		if opts.Component != 0 {
			// 组件id对应名称：https://github.com/apache/skywalking/blob/master/oap-server/server-starter/src/main/resources/component-libraries.yml
			span.SetComponent(opts.Component)
		}
		if opts.Layer != 0 {
			span.SetSpanLayer(opts.Layer)
		}
		if len(opts.Log) > 0 {
			span.Log(time.Now(), opts.Log...)
		}
	} else {
		err = fmt.Errorf("failed to create entry span of skywalking: %w", err)
	}
	return span, ctx, err
}

// CreateLocalSpan 创建内部自定span
func (p *TracerPkg) CreateLocalSpan(c context.Context, opts LocalSpanOptions) (go2sky.Span, context.Context, error) {
	if !p.CheckEnable() {
		return nil, c, errs.ErrTracerNotEnabled
	}
	span, newCtx, err := go2sky.GetGlobalTracer().CreateLocalSpan(
		c,
		go2sky.WithOperationName(opts.OperationName),
	)
	if err == nil {
		if opts.Component == 0 {
			opts.Component = ComponentIDGINHttpServer
		}
		span.SetComponent(opts.Component)

		if opts.Layer != 0 {
			span.SetSpanLayer(opts.Layer)
		}
		if len(opts.Log) > 0 {
			span.Log(time.Now(), opts.Log...)
		}
	} else {
		err = fmt.Errorf("failed to create local span of skywalking: %w", err)
	}
	return span, newCtx, err
}

// CreateExitSpan 创建对外请求span，返回的content为当前请求的主线context
func (p *TracerPkg) CreateExitSpan(c context.Context, opts ExitSpanOptions) (go2sky.Span, error) {
	if !p.CheckEnable() {
		return nil, errs.ErrTracerNotEnabled
	}
	if opts.Injector == nil {
		opts.Injector = func(key, value string) error {
			return nil
		}
	}
	span, err := go2sky.GetGlobalTracer().CreateExitSpan(c, opts.OperationName, opts.Peer, opts.Injector)

	if err == nil {
		if opts.Component == 0 {
			opts.Component = ComponentIDGINHttpServer
		}
		span.SetComponent(opts.Component)

		if opts.Layer != 0 {
			span.SetSpanLayer(opts.Layer)
		}
		if len(opts.Log) > 0 {
			span.Log(time.Now(), opts.Log...)
		}
	} else {
		err = fmt.Errorf("failed to create exit span of skywalking: %w", err)
	}
	return span, err
}

func (p *TracerPkg) CreateExitSpanWithContext(c context.Context, opts ExitSpanOptions) (go2sky.Span, context.Context, error) {
	if !p.CheckEnable() {
		return nil, nil, errs.ErrTracerNotEnabled
	}
	if opts.Injector == nil {
		opts.Injector = func(key, value string) error {
			return nil
		}
	}
	span, newCtx, err := go2sky.GetGlobalTracer().
		CreateExitSpanWithContext(c, opts.OperationName, opts.Peer, opts.Injector)

	if err == nil {
		if opts.Component == 0 {
			opts.Component = ComponentIDGINHttpServer
		}
		span.SetComponent(opts.Component)

		if opts.Layer != 0 {
			span.SetSpanLayer(opts.Layer)
		}
		if len(opts.Log) > 0 {
			span.Log(time.Now(), opts.Log...)
		}
	} else {
		err = fmt.Errorf("failed to create exit span with context of skywalking: %w", err)
	}
	return span, newCtx, err
}

func (p *TracerPkg) SpanTag(span go2sky.Span, tag go2sky.Tag, content string) {
	if span != nil {
		span.Tag(tag, content)
	}
}

func (p *TracerPkg) SpanErr(span go2sky.Span, content ...string) {
	if span != nil {
		span.Error(time.Now(), content...)
	}
}

func (p *TracerPkg) CheckEnable() bool {
	return go2sky.GetGlobalTracer() != nil
}

func (p *TracerPkg) EnableTracer() {
	go2sky.SetGlobalTracer(p.tracer)
}

func (p *TracerPkg) DisableTracer() {
	var emptyTracer *go2sky.Tracer
	go2sky.SetGlobalTracer(emptyTracer)
}
